#ifndef OPENGLDISPLAY_H
#define OPENGLDISPLAY_H

#include <QDebug>
#include <QGLWidget>
#include <QMouseEvent>
#include <GL/gl.h>
#include <GL/glu.h>
#include <QVector3D>

#include "robotglcontroller.h"

#define SELECT_BUF_SIZE 512
#define MALLOC_COMPONENT (GUIComponent*)malloc(sizeof(GUIComponent))

/**
 * @brief A class for displaying a robot with an OpenGL scene.
 *
 * The OpenGLDisplay class is the parent class of the views that can draw
 * our robots, like TroyGLView. It follows the MVC architecture, but it isn't
 * strict MVC.  Rather, we follow the MVP (Model View Presenter) architecture,
 * where the view handles some of the user input.  Namely, receives mouse input,
 * which is forwarded to the controller, and it also handles picking
 * objects in the scene using the openGL name stack picking method.  The
 * selections are sent to the controller, which does the calculations to change
 * the joint angles, etc.
 *
 * There are a few pure virtual methods that need to be implemented in the
 * subclasses. First, implement initComponents() to initialize the GUIComponents
 * for the scene (GUIComponents are used for object selection and can be
 * sublcassed to add functionality). Second, implement drawScene, to draw the
 * robot and populate the name stack as you are doing so.
 *
 * Initializing an OpenGLDisplay object requires you to load the controller
 * then model, IN THAT ORDER.  When the model is loaded, this view loads it
 * into the controller as well, so that the view and controller are
 * interacting with the same object.
*/
class OpenGLDisplay : public QGLWidget
{
    Q_OBJECT

public:
    /**
     * @brief Represents an object that has been rendered in openGL.
     *
     * The GUIComponent class contains basic information about
     * an openGL object, including its name(s) for picking.  It is
     * placed in a class, rather than a struct, so that inheritance
     * can be used if needed.
    */
    class GUIComponent{
    public:
        //Constructors
        GUIComponent(NameGroup names, bool selectable = true);
        GUIComponent(NameGroup names, RGBColor selectionColor,
                     bool selectable = true);

        //Accessors
        const unsigned int* names() const;
        const float* selectionColor() const;
        bool selectable() const;
        bool selected() const;

        void setSelectable(bool selectable);
        void setSelected(bool selected);

    private:
        NameGroup _names;
        RGBColor _selectionColor;
        bool _selectable;
        bool _selected;
    };


    //Constructor
    explicit OpenGLDisplay(QWidget *parent = 0);
    //Destructor
    virtual ~OpenGLDisplay();

    //Helpful functions to transform mouse positions
    //into the OpenGL standard (where 0,0 is the bottom
    //left corner, rather than top left)
    QPointF transformCoordinates(QPointF point);
    QPoint transformCoordinates(QPoint point);
    int transformYCoordinate(int y);
    //Transforms 2D screen coordinates into 3D coordinates
    void transformTo3D(float x, float y, double *posX, double *posY, double *posZ);

    //Utility functions to draw some simple geometries
    static void wireCube(float size);
    static void wireSphere(float radius, int slices, int stacks);
    static void shadedCube(float size);

    //Helper function to construct a matrix that will draw an object projected
    //as a shadow on an arbitrary plane
    static void shadowMatrix(GLfloat shadowMat[4][4], GLfloat groundplane[4], GLfloat lightpos[4]);

    //Establishes the connections between the view, controller,
    //and model
    virtual void loadController(RobotGLController* controller);
    virtual void loadModel(Robot* robot);

    //Function to select a component of the GUI
    virtual GUIComponent* selectComponent(NameGroup names);
    //Function to deselect a component of the GUI
    virtual void deselectComponent(NameGroup names);
    virtual void clearSelection();
    virtual bool isSelected(NameGroup names);

protected:
    //Re-implemented functions to do the OpenGL stuff
    virtual void initializeGL();
    virtual void resizeGL(int width, int height) = 0;
    virtual void paintGL();
    //Called whenever the perspective needs to be set
    //(see startPicking())
    virtual void setPerspective(int width, int height) = 0;

    //Mouse event functions
    virtual void mouseMoveEvent(QMouseEvent * event);
    virtual void mousePressEvent(QMouseEvent * event);
    virtual void mouseReleaseEvent(QMouseEvent* event);

    //Initializes the gui components for selection, etc.
    //Called by loadModel().  It is purely virtual because
    //only subclasses will know what components are selectable,
    //what their selection color is, etc.
    virtual void initComponents() = 0;

    //Pure virtual call to draw the scene to the openGL display
    virtual void drawScene() = 0;

    RobotGLController* _controller;
    Robot* _model;
    QList<GUIComponent*> _components;

private:
    //Functions that perform the openGL picking
    void startPicking(int cursorX, int cursorY);
    void processHits(int hits, unsigned int buffer[]);
    //Helper function to find a gui component by names
    GUIComponent* findComponent(NameGroup names);

signals:
    void glMousePress(double x, double y, double z);
    void glMouseRelease(double x, double y, double z);
    void glMouseMove(double x, double y, double z);
    void processingHits(int hitNum);
    void hitInfo(QString info);
    void componentSelected(const unsigned int* names);
    void selectionCleared();
};

#endif // OPENGLDISPLAY_H
